package com.spa.infrastructure.service.douyin;

import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.spa.domain.entity.DouYinOauth;
import com.spa.domain.service.DouYinOauthService;
import com.spa.infrastructure.enums.dict.base.SourceTypeEnum;
import com.spa.infrastructure.exception.SpaException;
import com.spa.infrastructure.repository.po.DouyinOauthPO;
import com.spa.infrastructure.service.douyin.param.web.*;
import com.spa.infrastructure.service.douyin.result.web.*;
import com.spa.infrastructure.util.OrderUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.annotation.Resource;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;

/**
 * 1、获取授权码code
 * 2、通过授权码code获取access_token
 * 3、通过access_token调用接口
 */
@Service
@Slf4j
public class DouYinWebService extends DouYinService{

    @Resource
    private DouYinOauthService douYinOauthService;

    @Value("${douYin.web.appId}")
    private String webAppId;

    @Value("${douYin.web.secret}")
    private String webSecret;

    public String getIframeByItemId(String itemId) throws JsonProcessingException {
        String itemIdBase64 = Base64.getEncoder().encodeToString(itemId.getBytes(StandardCharsets.UTF_8));
        String res = sendGet4DouYinV3(DouYinWebConstant.GET_IFRAME_BY_ITEM.replace("ITEMID", itemIdBase64).replace("APP_ID", webAppId));
        GetIframeByItemIdResult getIframeByItemIdResult = mapper.readValue(res, GetIframeByItemIdResult.class);
        return getIframeByItemIdResult.getData().getIframe_code();
    }

    public void addOrUpdateAccount(String code) throws JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        OauthAccessTokenResult oauthAccessTokenResult = getAccessToken(code);
        String openId = oauthAccessTokenResult.getData().getOpen_id();
        String accessToken = oauthAccessTokenResult.getData().getAccess_token();
        long expiresIn = oauthAccessTokenResult.getData().getExpires_in();
        String logId = oauthAccessTokenResult.getData().getLog_id();
        String refreshToken = oauthAccessTokenResult.getData().getRefresh_token();
        long refreshExpiresIn = oauthAccessTokenResult.getData().getRefresh_expires_in();
        String scope = oauthAccessTokenResult.getData().getScope();
        OauthUserInfoResult oauthUserInfoResult = getUserInfo(accessToken, openId);
        String avatar = oauthUserInfoResult.getData().getAvatar();
        String nickname = oauthUserInfoResult.getData().getNickname();
        String unionId = oauthUserInfoResult.getData().getUnion_id();
        String phone = "18888888888";
        if (ObjectUtil.isNotNull(oauthUserInfoResult.getData().getEncrypt_mobile())) {
            phone = decrypt(oauthUserInfoResult.getData().getEncrypt_mobile());
        }
        DouYinOauth douYinOauth = douYinOauthService.getByOpenId(openId);
        log.info("DouYinOauth {}", douYinOauth);
        if (ObjectUtil.isNotNull(douYinOauth)) {
            //更新
            DouyinOauthPO po = new DouyinOauthPO();
            po.setId(douYinOauth.getId());
            po.setAccessToken(accessToken);
            po.setExpiresDate(OrderUtils.toDate(LocalDateTime.now().plusSeconds(expiresIn)));
            po.setLogId(logId);
            po.setRefreshExpiresDate(OrderUtils.toDate(LocalDateTime.now().plusSeconds(refreshExpiresIn - 7200)));
            po.setRefreshToken(refreshToken);
            po.setScope(scope);
            po.setPhone(phone);
            douYinOauthService.updateDouYinAccountById(po);
            return;
        }
        //新增
        DouyinOauthPO po = new DouyinOauthPO();
        po.setOpenId(openId);
        po.setAccessToken(accessToken);
        po.setExpiresDate(OrderUtils.toDate(LocalDateTime.now().plusSeconds(expiresIn)));
        po.setLogId(logId);
        po.setRefreshExpiresDate(OrderUtils.toDate(LocalDateTime.now().plusSeconds(refreshExpiresIn - 7200)));
        po.setRefreshToken(refreshToken);
        po.setScope(scope);
        po.setAvatar(avatar);
        po.setNickname(nickname);
        po.setUnionId(unionId);
        po.setPhone(phone);
        douYinOauthService.addDouYinAccount(po);

    }

    /**
     * 该接口获取用户的抖音公开信息，包含昵称和头像，适用于抖音、抖音极速版。
     * @param accessToken
     * @param openId
     * @return
     * @throws JsonProcessingException
     */
    public OauthUserInfoResult getUserInfo(String accessToken, String openId) throws JsonProcessingException {
        String url = DouYinWebConstant.OAUTH_USERINFO;
        OauthUserInfoParam oauthUserInfoParam = OauthUserInfoParam.builder()
                .access_token(accessToken)
                .open_id(openId)
                .build();
        String response = sendPost4DouYinV2(url, mapper.writeValueAsString(oauthUserInfoParam));
        OauthUserInfoResult oauthUserInfoResult = mapper.readValue(response, OauthUserInfoResult.class);
        log.info("OauthUserInfoResult {}", oauthUserInfoResult);
        return oauthUserInfoResult;
    }

    public VideoListResult getVideoList(String accessToken, String openId, long cursor) throws JsonProcessingException {
        String url = DouYinWebConstant.VIDEO_LIST + "?count=20" + "&cursor=" + cursor + "&open_id=" + openId;
        String response = sendGet4DouYinV2(url, accessToken);
        VideoListResult videoListResult = mapper.readValue(response, VideoListResult.class);
        log.info("VideoListResult {}", videoListResult);
        return videoListResult;
    }

    public String poiSearch(String keyword, long cursor, int count, String city) throws JsonProcessingException {
        String accessToken = getClientToken(webAppId, webSecret, SourceTypeEnum.DouYinWEB);
        String url = DouYinWebConstant.POI_SEARCH + "?count=" + count + "&cursor=" + cursor + "&keyword=" + keyword + "&city=" + city;
        String response = sendGet4DouYinV2(url, accessToken);
//        VideoListResult videoListResult = mapper.readValue(response, VideoListResult.class);
        log.info("VideoListResult {}", response);
        return response;
    }

    public ItemCommentListResult getItemCommentList(String accessToken, String openId, long cursor, int count, String itemId) throws JsonProcessingException {
//        String itemIdBase64 = Base64.getEncoder().encodeToString(itemId.getBytes(StandardCharsets.UTF_8));
        String url = DouYinWebConstant.ITEM_COMMENT_LIST.replace("OPENID", openId)
                .replace("COUNT", String.valueOf(count))
                .replace("ITEMID", Base64Utils.encodeToString(itemId.getBytes(StandardCharsets.UTF_8)));
        String response = sendGet4DouYinV2(url, accessToken);
        ItemCommentListResult itemCommentListResult = mapper.readValue(response, ItemCommentListResult.class);
        log.info("ItemCommentListResult {}", itemCommentListResult);
        return itemCommentListResult;
    }

    public ItemCommentReplyResult replyComment(String accessToken, String commentId, String itemId, String content, String openId) throws JsonProcessingException {
        String url = DouYinWebConstant.ITEM_COMMENT_REPLY.replace("OPENID", openId);
        ItemCommentReplyParam itemCommentReplyParam = ItemCommentReplyParam.builder()
                .item_id(itemId)
                .comment_id(commentId)
                .content(content)
                .build();
        String response = sendPost4DouYinV3(url, mapper.writeValueAsString(itemCommentReplyParam), accessToken);
        ItemCommentReplyResult itemCommentReplyResult = mapper.readValue(response, ItemCommentReplyResult.class);
        log.info("ItemCommentReplyResult {}", itemCommentReplyResult);
        return itemCommentReplyResult;
    }

    public ItemCommentTopResult topComment(String accessToken, String commentId, String itemId, boolean top, String openId) throws JsonProcessingException {
        String url = DouYinWebConstant.ITEM_COMMENT_TOP.replace("OPENID", openId);
        ItemCommentTopParam itemCommentTopParam = ItemCommentTopParam.builder()
                .item_id(itemId)
                .comment_id(commentId)
                .top(top)
                .build();
        String response = sendPost4DouYinV3(url, mapper.writeValueAsString(itemCommentTopParam), accessToken);
        ItemCommentTopResult itemCommentTopResult = mapper.readValue(response, ItemCommentTopResult.class);
        log.info("ItemCommentTopResult {}", itemCommentTopResult);
        return itemCommentTopResult;
    }

    /**
     * 该接口在用户公开信息的基础上，额外获取用户的手机号。
     * 用户授权后，获取用户公开信息接口会额外返回 encrypt_mobile 字段。
     * 解密手机号，使用 AES 算法解密，秘钥是 client_secret， 向量 lv 是 client_secret 前 16 字节。
     * @param encryptedMobile 请先获取用户公开信息，才能获取用户手机号。
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public String decrypt(String encryptedMobile)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        String clientSecret = webSecret;
        byte[] clientSecretBytes = clientSecret.getBytes();
        SecretKey secretKey = new SecretKeySpec(clientSecretBytes, 0, clientSecretBytes.length, "AES");
        byte[] iv = Arrays.copyOfRange(clientSecretBytes, 0, 16);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        String algorithm = "AES/CBC/PKCS5Padding";
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(encryptedMobile));
        return new String(plainText);
    }

    /**
     * 该接口用于获取用户授权第三方接口调用的凭证 access_token；该接口适用于抖音授权。
     * @param code
     * @return
     * @throws JsonProcessingException
     */
    private OauthAccessTokenResult getAccessToken(String code) throws JsonProcessingException {
        OauthAccessTokenParam param = OauthAccessTokenParam.builder()
                .client_key(webAppId)
                .grant_type("authorization_code")
                .code(code)
                .client_secret(webSecret)
                .build();
        String result = sendPost4DouYinV2(DouYinWebConstant.OAUTH_ACCESS_TOKEN, mapper.writeValueAsString(param));
        OauthAccessTokenResult oauthAccessTokenResult = mapper.readValue(result, OauthAccessTokenResult.class);
        if (oauthAccessTokenResult.getData().getError_code() != 0) {
            throw new SpaException(oauthAccessTokenResult.getData().getDescription());
        }
        log.info("OauthAccessTokenResult : {}", oauthAccessTokenResult);
        return oauthAccessTokenResult;
    }

    /**
     * 该接口用于刷新 access_token 的有效期；该接口适用于抖音授权。
     * @param refreshToken
     * @return
     * @throws JsonProcessingException
     */
    private OauthAccessTokenResult refreshAccessToken(String refreshToken) throws JsonProcessingException {
        OauthRefreshTokenParam param = OauthRefreshTokenParam.builder()
                .client_key(webAppId)
                .grant_type("refresh_token")
                .refresh_token(refreshToken)
                .build();
        String result = sendPost4DouYinV4(DouYinWebConstant.OAUTH_REFRESH_TOKEN, mapper.writeValueAsString(param));
        OauthAccessTokenResult oauthAccessTokenResult = mapper.readValue(result, OauthAccessTokenResult.class);
        log.info("OauthAccessTokenResult : {}", oauthAccessTokenResult);
        return oauthAccessTokenResult;
    }

    /**
     * 该接口用于刷新 refresh_token 的有效期。该接口适用于抖音授权.
     * @param refreshToken
     * @return
     * @throws JsonProcessingException
     */
    public OauthReNewRefreshAccessTokenResult renewRefreshAccessToken(String refreshToken) throws JsonProcessingException {
        OauthReNewRefreshTokenParam param = OauthReNewRefreshTokenParam.builder()
                .client_key(webAppId)
                .refresh_token(refreshToken)
                .build();
        String result = sendPost4DouYinV4(DouYinWebConstant.OAUTH_RENEW_REFRESH_TOKEN, mapper.writeValueAsString(param));
        OauthReNewRefreshAccessTokenResult oauthAccessTokenResult = mapper.readValue(result, OauthReNewRefreshAccessTokenResult.class);
        log.info("OauthReNewRefreshAccessTokenResult : {}", oauthAccessTokenResult);
        return oauthAccessTokenResult;
    }

    /**
     * 根据抖音账户id获取AccessToken
     * @param id
     * @return
     */
    public DouYinOauth getAccessToken(long id) throws JsonProcessingException {
        DouYinOauth douYinOauth = douYinOauthService.getAccountById(id);
        //过期时间小于当期时间，刷新Token
        if (douYinOauth.getExpiresDate().after(new Date())) {
            return douYinOauth;
        }
        //当 access_token 过期（过期时间 15 天）后，可以通过该接口使用 refresh_token（过期时间 30 天）进行刷新。
        // 刷新后获得一个有效期为15天的 access_token，但是 refresh_token 的有效期保持不变。 更新AccessToken
        OauthAccessTokenResult oauthAccessTokenResult = refreshAccessToken(douYinOauth.getRefreshToken());
        if (oauthAccessTokenResult.getData().getError_code() == 0) {
            String token = oauthAccessTokenResult.getData().getAccess_token();
            DouyinOauthPO po = new DouyinOauthPO();
            po.setId(id);
            po.setAccessToken(token);
            po.setExpiresDate(OrderUtils.toDate(LocalDateTime.now().plusSeconds(oauthAccessTokenResult.getData().getExpires_in())));
            douYinOauthService.updateDouYinAccountById(po);
            douYinOauth.setAccessToken(token);
            return douYinOauth;
        } else {
            log.error("getAccessToken : {} {}",oauthAccessTokenResult.getMessage(),oauthAccessTokenResult.getData().getDescription());
            return null;
        }
    }

}
